/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 */
#include <stdio.h>
#include "securec.h"
#include "hlink_log.h"
#include "hardware_com.h"
#include "osal_types.h"
#include "osal.h"
#include "hlink_user_api.h"
#include "zone.h"

static hlink_log_level g_log_level = HLINK_LOG_NONE;
static osal_mutex_t g_log_print_mutex = cn_mutex_invalid; // log print lock to ensure that the log is printed correctly in multi threads
static char g_log_buf[HLINK_LOG_BUFF_LEN + 1] = { 0 };
static char g_log_record_buf[HLINK_LOG_BUFF_LEN + 1] = { 0 };
static hlink_log_upload_switch g_log_upload_switch[HLINK_UPLOAD_MAX] = {
    HLINK_LOG_UPLOAD_OFF, // gateway
    HLINK_LOG_UPLOAD_OFF, // telephone app
};
static hlink_log_module_info_para g_log_module_info[HLINK_LOG_MODULE_NUM_MAX] = {0};

zone_info g_cache_log_zone = {
	.id = LOGRECORD_ZONE,
	.start_address = 0x00000000,
	.end_address = 0x00000000 + HLINK_CACHE_LOG_BUFF_SIZE - 1,
};// 32K

typedef struct {
    hlink_log_level level;
    char *fmt_str;
    char *ori_str;
} hlink_log_level_fmt;

hlink_log_level_fmt g_log_level_fmt_map[] = {
    {HLINK_LOG_EMERG, "[EMEGR]:", "emerg"},
    {HLINK_LOG_ALERT, "[ALERT]:", "alert"},
    {HLINK_LOG_CRIT,  "[CRIT]:",  "crit"},
    {HLINK_LOG_ERROR, "[ERR]:",   "error"},
    {HLINK_LOG_WARN,  "[WARN]:",  "warn"},
    {HLINK_LOG_NOTIC, "[NOTIC]:", "notice"},
    {HLINK_LOG_INFO,  "[INFO]:",  "info"},
    {HLINK_LOG_DEBUG, "[DEBUG]:", "debug"},
};
static hlink_log_postion_info g_log_postion_info = {0}; // record flash newest log postion info to check repeated log


/*
 * 功能: 日志串口打印
 */
void hlink_board_printf(const char *str)
{
	printf("%s",str);
}

void hlink_printf(const char *format, ...)
{
    int ret;
    va_list argList;
    (void)memset_s(g_log_buf, sizeof(g_log_buf), 0, sizeof(g_log_buf));
    va_start(argList, format);
    ret = vsprintf_s(g_log_buf, sizeof(g_log_buf), format, argList);
    va_end(argList);
    (void)argList;
    if (ret == -1) {
        hlink_board_printf("vsprintf_s failed when hlink printf\n");
        return;
    }
    ret = strcat_s(g_log_buf, sizeof(g_log_buf), "\r\n");
    if (ret != EOK) {
        hlink_board_printf("strcat_s failed when hlink log\n");
        return;
    }
    hlink_board_printf(g_log_buf);
}

/**/
void hlink_set_log_switch(hlink_log_upload_switch status, hlink_log_upload_direction direction)
{
    g_log_upload_switch[direction] = status;
}
hlink_log_upload_switch hlink_get_log_switch(hlink_log_upload_direction direction)
{
    return g_log_upload_switch[direction];
}

void hlink_log_level_set(hlink_log_level level)
{
    g_log_level = level;
}

hlink_log_level hlink_log_level_get(void)
{
    return g_log_level;
}

char *hlink_get_level_string(hlink_log_level level)
{
    switch (level) {
        case HLINK_LOG_EMERG:
        case HLINK_LOG_ALERT:
        case HLINK_LOG_CRIT:
        case HLINK_LOG_ERROR:
            return "error";
        case HLINK_LOG_WARN:
        case HLINK_LOG_NOTIC:
        case HLINK_LOG_INFO:
            return "info";
        case HLINK_LOG_DEBUG:
            return "debug";
        default:
            return "debug";
    }
}

int hlink_log_get_level_by_string(const char *level_name, hlink_log_level *level)
{
    if (level == NULL) {
        return -1;
    }
    int fmt_list_count = sizeof(g_log_level_fmt_map) / sizeof(hlink_log_level_fmt);
    for (int i = 0; i < fmt_list_count; i++) {
        if (strcmp(g_log_level_fmt_map[i].ori_str, level_name) == 0) {
            *level = g_log_level_fmt_map[i].level;
            return 0;
        }
    }
    return -1;
}


hlink_log_level hlink_log_get_level(uint32_t infoId)
{
    return (hlink_log_level)(infoId & HLINK_LOGLEVEL_MASK);
}


const char *hlink_log_get_level_str(hlink_log_level level)
{
	return "[INFO]:";
}

zone_info *hlink_get_cache_log_zone_info()
{
	return &g_cache_log_zone;
}

void hlink_log_zone_init(void)
{
	zone_info * log_zone = hlink_get_cache_log_zone_info();
	if(log_zone->start_address == 0) {
		void * buff = malloc(HLINK_CACHE_LOG_BUFF_SIZE);
		if(NULL == buff) {
			hlink_printf("hlink cache log buffer malloc failed!");
			return;
		}
		memset(buff, 0, HLINK_CACHE_LOG_BUFF_SIZE);
		log_zone->start_address = (unsigned int)buff;
		log_zone->last_address = log_zone->start_address;
		log_zone->end_address = log_zone->start_address + HLINK_CACHE_LOG_BUFF_SIZE - 1;
	}
	hlink_printf("hlink cache log init successfully");
    return;
}


int hlink_cache_log_record(char *log_data, unsigned short log_len)
{
	zone_info *log_zone = hlink_get_cache_log_zone_info();

	if(NULL == log_data || log_len == 0 || log_zone->start_address == 0) {
		printf("[%s:%d]Invalid parameter\r\n", __FUNCTION__, __LINE__);
		return -1;
	}
	
	if(log_zone->last_address < log_zone->start_address ||
		log_zone->last_address > log_zone->end_address){
		printf("last_address out of bounds, last_address restart\r\n");
		log_zone->last_address = log_zone->start_address;
		memset((void *)log_zone->start_address, 0, HLINK_CACHE_LOG_BUFF_SIZE);
	}
	
	if(log_zone->last_address + log_len > log_zone->end_address) {
		log_zone->last_address = log_zone->start_address;
		printf("cache log is full, last_address restart\r\n");
		memset((void *)log_zone->start_address, 0, HLINK_CACHE_LOG_BUFF_SIZE);
	}
	memcpy((void *)log_zone->last_address, log_data, log_len);
	log_zone->last_address += log_len;
	return 0;
}

int hlink_read_cache_log(uint8_t *buff, unsigned int addr, unsigned short len)
{
	zone_info *log_zone = hlink_get_cache_log_zone_info();
	if(buff == NULL || log_zone == NULL) {
		printf("[%s:%d]Invalid parameter\r\n", __FUNCTION__, __LINE__);
		return -1;
	}
	if(addr < log_zone->start_address || addr+len > log_zone->last_address) {
		printf("read address out of bounds\r\n");
		return -1;
	}
	memcpy(buff, (void *)addr, len);
	return len;
}

int hlink_flash_log_read(uint8_t *buff, unsigned int addr, unsigned short len)
{
	zone_info *log_zone = mcu_get_flash_zone(LOGRECORD_ZONE);
	if(buff == NULL || log_zone == NULL) {
		printf("[%s:%d]Invalid parameter\r\n", __FUNCTION__, __LINE__);
		return -1;
	}
	if(addr < log_zone->start_address || addr+len > log_zone->last_address) {
		printf("read address out of bounds\r\n");
		return -1;
	}
	mcu_flash_read(buff, len, addr);
	return len;
}


uint32_t hlink_log_get_mid(uint32_t infoId)
{
    return infoId & HLINK_MID_MASK;
}

uint32_t hlink_log_get_upload_flag(uint32_t infoId)
{
    return (infoId & HLINK_UPLOAD_MASK) >> HLINK_LOG_UPLOAD_MOVE_BIT;
}

uint32_t hlink_log_get_flash_flag(uint32_t infoId)
{
    return (infoId & HLINK_FLASH_MASK) >> HLINK_LOG_FLASH_MOVE_BIT;
}

int hlink_log_module_para_check(hlink_log_module_info_para *log_module_info)
{
    int num = sizeof(g_log_module_info) / sizeof(hlink_log_module_info_para);

    if (log_module_info == NULL) {
        hlink_printf("module reg info is NULL");
        return -1;
    }

    // mid check
    if (log_module_info->mid < 0x00010000) {
        hlink_printf("mid[%08x] is invalid, must greater than 0x00010000", log_module_info->mid);
        return -1;
    }

    // mid name check
    if (strlen(log_module_info->mid_name) == 0) {
        hlink_printf("mid_name is invalid");
        return -1;
    }

    // check mid/mid_name has registered
    for (int i = 0; i < num; i++) {
        if (g_log_module_info[i].mid == log_module_info->mid) {
            hlink_printf("mid[%08x] has registered before", log_module_info->mid);
            return -1;
        }
        if (strcmp(g_log_module_info[i].mid_name, log_module_info->mid_name) == 0) {
            hlink_printf("mid_name[%s] has registered before", log_module_info->mid_name);
            return -1;
        }
    }
    return 0;
}

void hlink_log_module_register(hlink_log_module_info_para *log_module_info)
{
    int num = sizeof(g_log_module_info) / sizeof(hlink_log_module_info_para);

    if (hlink_log_module_para_check(log_module_info) != HLINK_OK) {
        return;
    }

    for (int i = 0; i < num; i++) {
        if (g_log_module_info[i].mid == 0) {
            g_log_module_info[i].mid = log_module_info->mid;
            if (strcpy_s(g_log_module_info[i].mid_name, sizeof(g_log_module_info[i].mid_name),
                         log_module_info->mid_name) != EOK) {
                hlink_printf("module reg strcpy failed");
            }
            g_log_module_info[i].log_level = log_module_info->log_level;
            hlink_printf("log module info register successfully, mid[0x%08x], mid_name[%s], level[%d]",
                         log_module_info->mid, log_module_info->mid_name, log_module_info->log_level);
            return;
        }
    }
    hlink_printf("log module info register failed by full log module buffer, max buffer num is [%d]", num);
    return;
}

static int hlink_log_init(void)
{
	if(g_log_print_mutex != cn_mutex_invalid) {
		return 0;
	}
    int ret = osal_mutex_create(&g_log_print_mutex);
    if (ret != OSAL_OK) {
        hlink_printf("hlink log create mutex failed");
        return -1;
    }
	return 0;
}


int hlink_log_get_module_info(uint32_t mid, hlink_log_module_info_para *module_info)
{
    int num = sizeof(g_log_module_info) / sizeof(hlink_log_module_info_para);
    for (int i = 0; i < num; i++) {
        if (g_log_module_info[i].mid == mid) {
            module_info->mid = mid;
            if (strcpy_s(module_info->mid_name, sizeof(module_info->mid_name), g_log_module_info[i].mid_name) != EOK) {
                hlink_printf("hlink log header strcpy mid name failed");
            }
            module_info->log_level = g_log_module_info[i].log_level;
            return 0;
        }
    }
    hlink_printf("hlink has not found module info:mid[%08x]", mid);
	return 0;
}

int hlink_log_para_check(const hlink_log_infoid_para *info_para, const hlink_log_module_info_para *module_info)
{
    if (g_log_level == HLINK_LOG_NONE) {
        /* 日志级别为初始态HLINK_LOG_NONE时 以各模块注册时的级别为准 */
        if ((module_info->log_level == HLINK_LOG_NONE) || (info_para->level > module_info->log_level)) {
            return -1;
        }
        return 0;
    }
    if (info_para->level > g_log_level) {
        return -1;
    }
	return 0;
}


int hlink_log_impl_preproc(hlink_log_infoid_para *info_para, uint32_t infoId)
{
    hlink_log_module_info_para module_info = {0};

    if (hlink_log_init() != 0) {
        return -1;
    }
	
    info_para->mid = hlink_log_get_mid(infoId);
    info_para->flash = hlink_log_get_flash_flag(infoId);
    info_para->level = hlink_log_get_level(infoId);
    info_para->upload = hlink_log_get_upload_flag(infoId);

    if (hlink_log_get_module_info(info_para->mid, &module_info) != 0) {
        return -1;
    }

    if (hlink_log_para_check(info_para, &module_info) != 0) {
        return -1;
    }

    (void)memset_s(info_para->mid_name, sizeof(info_para->mid_name), 0, sizeof(info_para->mid_name));
    if (strcpy_s(info_para->mid_name, sizeof(info_para->mid_name), module_info.mid_name) != EOK) {
        hlink_printf("mid name strcpy failed");
        return -1;
    }
    return 0;
}

int hlink_log_creat_header(char *buf, unsigned short buf_len, hlink_log_level level, const char *mid_name)
{
    int log_header_len = 0;
    mcu_tm tm;
    omip_mcu_localtime(&tm);
    const char *fmt_str = hlink_log_get_level_str(level);
    log_header_len = snprintf_s(buf, buf_len, buf_len - 1,
                                "[%d-%02d-%02d %02d:%02d:%02d][%s]%s",
                                tm.tm_year, tm.tm_mon, tm.tm_mday,
                                tm.tm_hour, tm.tm_min, tm.tm_sec, mid_name, fmt_str);
    return log_header_len;
}


int hlink_log_impl(const hlink_log_infoid_para *info_para, va_list argList, const char *format,
                   char *log_buf, size_t buf_len)
{
    int log_header = 0;

    (void)memset_s(log_buf, buf_len, 0, buf_len);
    int ret = hlink_log_creat_header(log_buf, buf_len, info_para->level, info_para->mid_name);
    if ((ret >= 0) && (ret < (int)buf_len)) {
        log_header += ret;
    } else {
        hlink_board_printf("snprintf_s failed when create hlink log head\n");
        return -1;
    }

    ret = vsprintf_s(log_buf + log_header, buf_len - 1 - log_header, format, argList);
    if (ret == -1) {
        hlink_board_printf("vsprintf_s failed when hlink log\n");
        return ret;
    }
    ret = strcat_s(log_buf, buf_len, "\r\n");
    if (ret != EOK) {
        hlink_board_printf("strcat_s failed when hlink log\n");
        return ret;
    }
    hlink_board_printf(log_buf);
    return 0;
}

void hlink_flash_log_write(const char *buf, unsigned short len)
{
	zone_info *zone = mcu_get_flash_zone(LOGRECORD_ZONE);
	if(zone->last_address < zone->start_address || zone->last_address > zone->end_address) {
		zone->last_address = zone->start_address;
		hlink_printf("last_address out of bounds,restart, last_address=0x%x!\r\n", zone->last_address);
		flash_erase_zone(LOGRECORD_ZONE);
	}
	
	if(zone->last_address + len > zone->end_address) {
		zone->last_address = zone->start_address;
		hlink_printf("flash zone is full restart!\r\n");
		flash_erase_zone(LOGRECORD_ZONE);
	}
	mcu_flash_write(buf, len, zone->last_address);
	zone->last_address += len;
	return;
}

void hlink_log_record_flash_proc(const char *func, int32_t line_number, const hlink_log_infoid_para *info_para)
{
	
	hlink_flash_log_write(g_log_record_buf, strlen(g_log_record_buf));
    return;
}

void hlink_log_record_flash(const char *func, int32_t line_number, const hlink_log_infoid_para *info_para)
{
    if (info_para->flash != HLINK_LOG_FLASH) {
        return;
    }

    hlink_log_record_flash_proc(func, line_number, info_para);
}

void hlink_log_impl_proc_normal(hlink_log_infoid_para *info_para, const hlink_log_postion_info *log_postion_info,
                                int need_upload, va_list argList, const char *format)
{
    if (osal_mutex_lock(g_log_print_mutex) != OSAL_OK) {
        hlink_printf("hlink log print mutex lock failed");
        return;
    }
	/* 1、日志格式拼接和串口打印 */
	int ret = hlink_log_impl(info_para, argList, format, g_log_record_buf, sizeof(g_log_record_buf));
    if (ret != 0) {
        (void)osal_mutex_unlock(g_log_print_mutex);
        return;
    }
	
    /* 2、日志写入flash*/
    hlink_log_record_flash(log_postion_info->func_name, log_postion_info->line_number, info_para);

    /* 3、mcu_project add, 日志写入cache。注意：仅INFO ERROR级别写cache */
    if(info_para->level <= HLINK_LOG_INFO)
    {
        if (g_log_record_buf != NULL) {
        	hlink_cache_log_record(g_log_record_buf, strlen(g_log_record_buf));
        }
    }

	osal_mutex_unlock(g_log_print_mutex);
	return;
}

void hlink_log_common_impl(const hlink_log_postion_info *log_postion_info, uint32_t infoId,
                           int need_upload, va_list argList, const char *format)
{
    int ret = 0;
    hlink_log_infoid_para info_para = {0};

    ret = hlink_log_impl_preproc(&info_para, infoId);
    if (ret != 0) {
        return;
    }

    if ((g_log_upload_switch[HLINK_UPLOAD_APP] == HLINK_LOG_UPLOAD_ON) &&
        (strcmp(info_para.mid_name, "OMIP") == 0)) {
    	/* 模组侧，OMIP模块日志调用 */
        //hlink_log_impl_proc_special(&info_para, log_postion_info, argList, format);
    } else {
        hlink_log_impl_proc_normal(&info_para, log_postion_info, need_upload, argList, format);
    }
    return;
}


void hlink_log_api(const char *func, int32_t line_number, uint32_t infoId, const char *format, ...)
{
	
	hlink_log_postion_info log_postion_info = {0};

	if ((func == NULL) || (format == NULL)) {
		hlink_printf("hlink log api para check NULL");
		return;
	}
	if (strcpy_s(log_postion_info.func_name, sizeof(log_postion_info.func_name), func) != EOK) {
		hlink_printf("hlink log api postion info strcpy failed");
		return;
	}
	
	log_postion_info.line_number = line_number;
	va_list argList;
	va_start(argList, format);
	hlink_log_common_impl(&log_postion_info, infoId, 1, argList, format);
	va_end(argList);
	(void)argList;
}



